#include <Lucnx/string.h>
#include <Lucnx/stdlib.h>
#include <Lucnx/memory.h>
#include <Lucnx/debug.h>
#include <Lucnx/acpi.h>
#include <Lucnx/io.h>

#define BIOS_DATA 0x100000 // 把 BIOS 在内存顶部放置的数据映射到的虚拟地址

acpi_pointer *rsdp;
fadt_t *fadt;
rsdt_t *rsdt;

// ACPI 表测试
static bool checksum(char *ptr, size_t length)
{
    char sum = 0;
    for (size_t i = 0; i < length; i++)
    {
        sum += ptr[i];
    }
    return sum == 0;
}

// 寻找 rsdp
acpi_pointer *search_rsdp()
{
    u32 *ptr = (u32*)0xe0000;
    for ( ; (u32)ptr <= 0x100000; ptr++)
    {
        if (!memcmp(ptr, "RSD PTR ", 8))
        {
            if (checksum(ptr, ((acpi_pointer*)ptr)->length))
            {
                return ptr;
            }
        }
    }
    panic("acpi:can't find 'rsdp'\n");
    return NULL;
}

// 寻找 signatrue 相同的 ACPI 表
static void *search_tables(char *signatrue)
{
    int nr = (rsdt->header.length - sizeof(rsdt->header)) / 4; // 表项数量
    for (size_t i = 0; i < nr; i++)
    {
        acpi_desc_header *header = (acpi_desc_header *) ((u32)rsdt->entry + i * 4);
        u32 _header = (u32) header;
        _header = _header & 0xFFF + BIOS_DATA;
        header = (acpi_desc_header *) _header;
        if (!memcmp(header->signature, signatrue, 4))
        {
            return (void*)header;
        }
    }
    return NULL;
}

void acpi_init()
{
    rsdp = search_rsdp();

    rsdt = (rsdt_t*) rsdp->rsdt_address;
    page_map((void*)BIOS_DATA, (void*)((u32)rsdt & 0xFFFFF000)); // 做映射 方便在虚拟地址中访问
    rsdt = (rsdt_t *) (BIOS_DATA + ((u32)rsdt & 0xFFF));
    assert(checksum(rsdt, rsdt->header.length));

    fadt = (fadt_t *) ((u32)search_tables("FACP") & 0xFFF + BIOS_DATA);
    assert(checksum(fadt, fadt->header.length));

    // 下面 27 行抄的也是 powerintdos 
    //  以后学会了自己重新写
    if (!(inw(fadt->PM1aControlBlock) & 1))
    {
        if (fadt->SMI_CommandPort && fadt->AcpiEnable)
        {
            outb(fadt->SMI_CommandPort, fadt->AcpiEnable);
            for (size_t i = 0; i < 300; i++)
            {
                if (inw(fadt->PM1aControlBlock) & 1)
                {
                    break;
                    for (int j = 0; j < 1000000; j++)
                        ;
                }
            }
            if (fadt->PM1bControlBlock)
            {
                for (int i = 0; i < 300; i++) 
                {
                    if (inw(fadt->PM1bControlBlock) & 1)
                       break;
                    for (int j = 0; j < 1000000; j++)
                       ;
                }
            }
        }
    }
}
/*
    ctrl cv 的 源自 powerintdos-386/kernel/drivers/acpi.c by min0911_ & zhouzhihao
    以后学会了自己重新写
*/
int acpi_shutdown()
{
    int i;
    unsigned short SLP_TYPa, SLP_TYPb;
    acpi_desc_header *header = (acpi_desc_header *)search_tables("DSDT");
    char *S5Addr = (char *)header;
    int dsdtLength = (header->length - sizeof(acpi_desc_header)) / 4;

  for (i = 0; i < dsdtLength; i++) {
    if (memcmp(S5Addr, "_S5_", 4) == 0)
      break;
    S5Addr++;
  }
  if (i < dsdtLength) {
    if ((*(S5Addr - 1) == 0x08 ||
         (*(S5Addr - 2) == 0x08 && *(S5Addr - 1) == '\\')) && *(S5Addr + 4) == 0x12) {
      S5Addr += 5;
      S5Addr += ((*S5Addr & 0xc0) >> 6) + 2;

      if (*S5Addr == 0x0a)
        S5Addr++;
      SLP_TYPa = *(S5Addr) << 10;
      S5Addr++;

      if (*S5Addr == 0x0a)
        S5Addr++;
      SLP_TYPb = *(S5Addr) << 10;
      S5Addr++;
    }
    // 关于PM1x_CNT_BLK的描述见 ACPI Specification Ver6.3 4.8.3.2.1
    outw(fadt->PM1aControlBlock, SLP_TYPa | 1 << 13);
    if (fadt->PM1bControlBlock != 0) {
      outw(fadt->PM1bControlBlock, SLP_TYPb | 1 << 13);
    }
  }
  return 1;
}